/*
 Copyright (C) 1996-1997 GX Media, Inc.

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "stdafx.h"
#include "Quake.h"
#include "Entity.h"
#include "Texture.h"
#include "LCommon.h"
#include "QDraw.h"
#include "QMainFrame.h"
#include "Qoole.h"
#include "ProgressWindow.h"

#ifdef _WIN32
#define DOT_EXE wxT(".exe")
#else
#define DOT_EXE
#endif

#define QBSP_DEFAULT_PATH wxT("/bin/qbsp") DOT_EXE
#define LIGHT_DEFAULT_PATH wxT("/bin/light") DOT_EXE
#define VIS_DEFAULT_PATH wxT("/bin/vis") DOT_EXE

#ifdef _WIN32
#define QUAKE_DEFAULT_NAME wxT("WinQuake") DOT_EXE
#else
#define QUAKE_DEFAULT_NAME wxT("quake")
#endif

Quake::Quake(void) :
	Game(GetNumUtils())
{
	gameName = wxT("Quake");
	palDef = wxT("quake.pal");
	defTexture = wxT("bricka2_4");
	texExt = wxT("");
	exportWad = true;
	palName = palDef;

	cfg = new LConfig(gameName);
	QDraw::OutputText(_("Loading %s settings... "), gameName.c_str());

	gameDir = wxT("");
	cfg->RegisterVar(wxT("GameDir"), &gameDir, LVAR_WXSTR);

	setPIndex = 0;
	cfg->RegisterVar(wxT("SetParamIndex"), &setPIndex, LVAR_INT);

	runUtilsFlag = 0x0001;
	cfg->RegisterVar(wxT("RunUtils"), &runUtilsFlag, LVAR_INT);

	// QBSP.
	utilsPath.resize(4);
	utilsParams.resize(4);
	utilsPath[0] = LFile::GetInitDir() + QBSP_DEFAULT_PATH;
	cfg->RegisterVar(wxT("UtilPath1"), &utilsPath[0], LVAR_WXSTR);

	utilsParams[0] = wxT("%file%");
	cfg->RegisterVar(wxT("UtilParam1"), &utilsParams[0], LVAR_WXSTR);

	// LIGHT
	utilsPath[1] = LFile::GetInitDir() + LIGHT_DEFAULT_PATH;
	cfg->RegisterVar(wxT("UtilPath2"), &utilsPath[1], LVAR_WXSTR);

	utilsParams[1] = wxT("%file%");
	cfg->RegisterVar(wxT("UtilParam2"), &utilsParams[1], LVAR_WXSTR);

	// VIS
	utilsPath[2] = LFile::GetInitDir() + VIS_DEFAULT_PATH;
	cfg->RegisterVar(wxT("UtilPath3"), &utilsPath[2], LVAR_WXSTR);

	utilsParams[2] = wxT("%file%");
	cfg->RegisterVar(wxT("UtilParam3"), &utilsParams[2], LVAR_WXSTR);

	// Quake
	utilsPath[3] = QUAKE_DEFAULT_NAME;
	cfg->RegisterVar(wxT("GamePath"), &utilsPath[3], LVAR_WXSTR);

	utilsParams[3] = wxT("+map %file%");
	cfg->RegisterVar(wxT("GameParam"), &utilsParams[3], LVAR_WXSTR);

	QDraw::OutputText(_("OK.\n"));
}

Quake::~Quake(void)
{
	cfg->SaveVars();
	delete cfg;
}

bool Quake::Init(void)
{
	if (initialized)
		return true;

	if (gameDir.empty() || !LFile::ExistDir(gameDir))
	{
		wxDirDialog dirDialog(GetMainFrame(), _("Locate Quake Folder"));

		if (dirDialog.ShowModal() != wxID_OK)
			return false;

		gameDir = dirDialog.GetPath();
	}

	baseDir = gameDir + wxT("/id1");
	mapDir = baseDir + wxT("/maps");

	texDB = new TexDB(this);

	texDir = LFile::GetInitDir() + wxT("/wads");

	wxString wadFile = texDir + wxT("/Quake.wad");
	texDB->AddTexDir(wadFile);

	initialized = true;

	wxString pak0File = baseDir + wxT("/pak0.pak");
	wxString pak1File = baseDir + wxT("/pak1.pak");

	LFile::UseDir(baseDir);
	pak0 = LFile::UsePak(pak0File);
	pak1 = LFile::UsePak(pak1File);

	// Verify game exe.
	if (!LFile::Exist(utilsPath[GetNumUtils() - 1]))
	{
		utilsPath[GetNumUtils() - 1] = gameDir + wxT("/") + QUAKE_DEFAULT_NAME;
	}

	ExtractTextures();

	return true;
}

WireFrameGeom *
Quake::LoadModel(const wxString &filename)
{
	int verts, edges;
	vec3_t *vert;
	edgenum_t *edgenum;
	int i, j, v;
	int v1, v2;
	edge_t *edge;
	char ident[8];
	mdlheader_t mdlheader;
	int group;
	int type;
	itriangle_t *tri;
	trivertx_t *trivert;

	verts = 0;

	LFile file;
	QDraw::OutputText(_("Loading model: %s... "), filename.c_str());

	if (!file.Open(filename))
	{
		QDraw::OutputText(_("Error. Couldn't open the file.\n"));
		return NULL;
	}

	file.Read(&ident, 4, 1);
	ident[4] = 0;
	file.Seek(0);

	file.Read(&mdlheader, sizeof(mdlheader_t), 1);

	for (i = 0; i < mdlheader.numskins; i++)
	{
		file.Read(&group, sizeof(group), 1);
		if (group)
		{
			file.Read(&group, sizeof(group), 1);
			file.SeekCur(group * (4 + mdlheader.skinwidth
					* mdlheader.skinheight));
		}
		else
			file.SeekCur(mdlheader.skinwidth * mdlheader.skinheight);
	}

	file.SeekCur(mdlheader.numverts * 12);

	tri = new itriangle_t[mdlheader.numtris];
	trivert = new trivertx_t[mdlheader.numverts];
	edge = new edge_t[mdlheader.numtris * 3];
	vert = new vec3_t[mdlheader.numtris * 3];

	file.Read(tri, sizeof(itriangle_t), mdlheader.numtris);

	file.Read(&type, sizeof(type), 1);
	if (type)
		file.SeekCur(8 + 4 * mdlheader.numframes);

	file.SeekCur(24);
	file.Read(trivert, sizeof(trivertx_t), mdlheader.numverts);
	for (i = 0; i < mdlheader.numverts; i++)
	{
		vert[i].x = trivert[i].x * mdlheader.scale.x + mdlheader.origin.x;
		vert[i].y = trivert[i].y * mdlheader.scale.y + mdlheader.origin.y;
		vert[i].z = trivert[i].z * mdlheader.scale.z + mdlheader.origin.z;
	}

	edges = 0;

	for (i = 0; i < mdlheader.numtris; i++)
	{
		edge[edges].vert[0] = vert[tri[i].vertices[0]];
		edge[edges].vert[1] = vert[tri[i].vertices[1]];
		edges++;
		edge[edges].vert[0] = vert[tri[i].vertices[1]];
		edge[edges].vert[1] = vert[tri[i].vertices[2]];
		edges++;
		edge[edges].vert[0] = vert[tri[i].vertices[2]];
		edge[edges].vert[1] = vert[tri[i].vertices[0]];
		edges++;
	}

	edgenum = new edgenum_t[edges];

	v = 0;
	for (i = 0; i < edges; i++)
	{
		v1 = -1;
		v2 = -1;
		for (j = 0; j < v; j++)
		{
			if (vert[j].x == edge[i].vert[0].x && vert[j].y
					== edge[i].vert[0].y && vert[j].z == edge[i].vert[0].z)
				v1 = j;
			if (vert[j].x == edge[i].vert[1].x && vert[j].y
					== edge[i].vert[1].y && vert[j].z == edge[i].vert[1].z)
				v2 = j;
		}

		if (v1 == -1)
			v1 = v++;
		if (v2 == -1)
			v2 = v++;

		vert[v1] = edge[i].vert[0];
		vert[v2] = edge[i].vert[1];

		edgenum[i].v1 = v1;
		edgenum[i].v2 = v2;
	}

	verts = v;
	ASSERT(verts > 0);

	LinkList<Edge3d> edgeList;
	Edge3d *newEdge;

	for (i = 0; i < edges; i++)
	{
		for (j = 0; j < i; j++)
		{
			if (edgenum[i].v1 == edgenum[j].v1 && edgenum[i].v2
					== edgenum[j].v2)
				continue;
			if (edgenum[i].v2 == edgenum[j].v1 && edgenum[i].v1
					== edgenum[j].v2)
				continue;
		}

		newEdge = new Edge3d(edgenum[i].v1, edgenum[i].v2);
		edgeList.AppendNode(*newEdge);
	}

	delete[] tri;
	delete[] trivert;
	delete[] edge;
	delete[] edgenum;

	QDraw::OutputText(_("OK.\n"));
	// it is WireFrameGeom's job to delete [] vert
	return new WireFrameGeom(verts, (GeomWFVertex *) vert, edgeList);
}

bool Quake::LoadTexture(Texture *texture, const wxString &filename, int offset)
{
	texture_header_t header;
	int size;

	LFile file;
	QDraw::OutputText(_("Loading texture: %s... \n"), texture->GetName().c_str());

	if (!file.Open(filename))
	{
		QDraw::OutputText(_("Error. Couldn't open file.\n"));
		return false;
	}

	file.Seek(offset);
	file.Read(&header, sizeof(header), 1);

	if (header.width <= 0 || header.width > 1024)
	{
		QDraw::OutputText(_("Error. The texture width is erroneous.\n"), filename.c_str());
		return false;
	}
	if (header.height <= 0 || header.height > 1024)
	{
		QDraw::OutputText(_("Error. The texture height is erroneous.\n"), filename.c_str());
		return false;
	}

	size = header.width * header.height;

	texture->mips = 4;
	texture->mip[0] = malloc(size);
	texture->mip[1] = malloc(size / 4);
	texture->mip[2] = malloc(size / 16);
	texture->mip[3] = malloc(size / 64);

	file.Seek(offset + header.mip1);
	file.Read(texture->mip[0], size, 1);
	file.Seek(offset + header.mip2);
	file.Read(texture->mip[1], size / 4, 1);
	file.Seek(offset + header.mip3);
	file.Read(texture->mip[2], size / 16, 1);
	file.Seek(offset + header.mip4);
	file.Read(texture->mip[3], size / 64, 1);

	texture->surface = texture->mip[0];

	texture->realWidth = header.width;
	texture->realHeight = header.height;
	texture->bits = 8;

	return true;
}

bool paks2wad(const wxString &quake_root, const wxString &wad_output,
		bool(*ProgFunc)(int p));

void Quake::ExtractTextures(void)
{
	wxString wadDir = LFile::GetInitDir() + wxT("/wads");
	wxString wadFile = wadDir + wxT("/Quake.wad");

	if (LFile::Exist(wadFile))
		return;

	if (wxMessageBox(wxT("Extract Quake Textures?"), wxGetApp().GetAppName(),
			wxYES_NO) == wxNO)
		return;

	if (!LFile::ExistDir(wadDir))
	{
#ifdef _WIN32
		_mkdir(wadDir.utf8_str());
#else
		mkdir(wadDir.utf8_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
	}

	ProgressFunc
			progFunc(wxT("Texture Extraction Progress"), wxT("Extracting"));
	if (!paks2wad(baseDir, wadFile, ProgressFunc::SetPos))
	{
		QDraw::OutputText(_("Failed to extract Quake textures.\n"));
	}
}

//===== Game Map Compile Stuff =====
const wxString &Quake::GetUtilName(int index) const
{
	static wxString utilNames[] =
	{ wxT("QBSP"), wxT("Light"), wxT("Vis"), wxT("Quake") };
	ASSERT(index >= 0 && index < 4);
	return utilNames[index];
}

wxString Quake::GetLeakFile(const wxString &docName) const
{
	static char leakPath[256];

	ASSERT(!docName.empty());

	if (!LFile::ExistDir(mapDir))
		return wxEmptyString;

	return mapDir + wxT("/") + docName + wxT(".pts");
}

Object *Quake::LoadLeakFile(const wxString &docName) const
{
	LFile leakFile;
	QDraw::OutputText(_("Loading leak information: %s... "), GetLeakFile(docName).c_str());
	if (!leakFile.Open(GetLeakFile(docName)))
	{
		QDraw::OutputText(_("Error.\nThere's no leak file.\n"));
		return NULL;
	}

	char *inLine;
	float x, y, z;
	Vector3d v0, v1, v2;
	Vector3d posVec;
	Line currLine;
	bool firstSeg = true;

	// Read the first point.
	while (!leakFile.EndOfFile())
	{
		if ((inLine = leakFile.GetNextLine()) != NULL && sscanf(inLine,
				"%f %f %f", &x, &y, &z) == 3)
		{
			v0.NewVector(x, y, z);
			v1 = v0;
			break;
		}
	}

	Object *rtnVal = new Object;
	Object *pNewSeg;
	Geometry *pSegBrush;

	// Read the rest.
	int count = 0;
	while (!leakFile.EndOfFile())
	{
		if ((inLine = leakFile.GetNextLine()) != NULL && sscanf(inLine,
				"%f %f %f", &x, &y, &z) == 3)
		{
			v2.NewVector(x, y, z);
			if (firstSeg)
			{
				Vector3d norm;
				norm.SubVector(v2, v1);
				currLine.NewLine(v1, norm);
				firstSeg = false;
			}
			if (!currLine.IsOnLine(v2, 1.0f))
			{
				pSegBrush = Geometry::MakeSegment(v0, v1);
				pSegBrush->CenterGeometry(posVec);
				pNewSeg = new Object;
				pNewSeg->SetBrush(pSegBrush);
				pNewSeg->SetPosition(posVec);
				rtnVal->AddChild(*pNewSeg, false);
				v0 = v1;
				firstSeg = true;
				if (++count > 300)
					break;
			}
			v1 = v2;
		}
	}

	if (rtnVal->GetNumChildren() == 0)
	{
		delete rtnVal;
		rtnVal = NULL;
	}
	else
	{
		rtnVal->SetBoundRadius();
	}

	QDraw::OutputText(_("OK.\n"));
	return rtnVal;
}
